#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using Grawlix.Application.Gui.Messages;
using Grawlix.Geometry;
using Grawlix.Services;
using OpenTK;
using OpenTK.Input;

namespace Grawlix.Client
{
	/// <summary>
	/// Translates between game keys and operating system keys.
	/// </summary>
	public class InputTranslator : IDisposable
	{
		/// <summary>
		/// The game window to attach to.
		/// </summary>
		private readonly GameWindow _gameWindow;

		/// <summary>
		/// Whether this object has been disposed.
		/// </summary>
		private bool _disposed;

		/// <summary>
		/// The place to report messages.
		/// </summary>
		private Domain _game;

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="InputTranslator"/> class.
		/// </summary>
		/// <param name="gameWindow">The game window to attach to.
		/// </param>
		public InputTranslator(GameWindow gameWindow)
		{
			gameWindow.Keyboard.KeyDown += OnKeyDown;
			gameWindow.Keyboard.KeyUp += OnKeyUp;
			//gameWindow.Keyboard.Character += OnCharacter;
			//gameWindow.Mouse.Move += OnMouseMove;
			gameWindow.Mouse.ButtonDown += OnMouseDown;
			gameWindow.Mouse.ButtonUp += OnMouseUp;
			//gameWindow.Mouse.Wheel += OnMouseWheel;
			_gameWindow = gameWindow;
		}

		/// <summary>
		/// Gets or sets the game to route messages to.
		/// </summary>
		/// <value>The game to route messages to.</value>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public Domain Game
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("InputTranslator");
				return _game;
			}
			set
			{
				if (_disposed)
					throw new ObjectDisposedException("InputTranslator");
				_game = value;
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing,
		/// releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed
		/// and unmanaged resources; <c>false</c> to release only
		/// unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				Unlist();
				_disposed = true;
			}
		}

		/// <summary>
		/// Releases unmanaged resources and performs other cleanup
		/// operations before the
		/// <see cref="InputTranslator"/> is reclaimed by garbage
		/// collection.
		/// </summary>
		~InputTranslator()
		{
			Dispose(false);
		}

		/// <summary>
		/// Finds the name of a virtual key.
		/// </summary>
		/// <param name="code">The virtual key code.</param>
		/// <returns>The name of the key.</returns>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public string LookupKeyCode(int code)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			return ((Key)code).ToString();
		}

		/// <summary>
		/// Finds the name of a mouse button.
		/// </summary>
		/// <param name="code">The mouse button.</param>
		/// <returns>The name of the button.</returns>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public string LookupMouseCode(int code)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			return ((MouseButton)code).ToString();
		}

		/// <summary>
		/// Called when a character is received.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <param name="character">The character received.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnCharacter(KeyboardDevice sender,
		                        char character)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
				_game.Post(new CharacterInputMessage
					{Character = character});
		}

		/// <summary>
		/// Called when a key is pressed.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <param name="key">The key pressed.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnKeyDown(KeyboardDevice sender, Key key)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
			{
				var translate = (int)key;
				_game.Post(new KeyDownInputMessage {Code = translate});
			}
		}

		/// <summary>
		/// Called when a key is released.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <param name="key">The key released.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnKeyUp(KeyboardDevice sender, Key key)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
			{
				var translate = (int)key;
				_game.Post(new KeyUpInputMessage {Code = translate});
			}
		}

		/// <summary>
		/// Called when the mouse is pressed.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <param name="button">The button pressed.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnMouseDown(MouseDevice sender,
		                        MouseButton button)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
			{
				var where = new Vector2(sender.X, sender.Y);
				var translate = (int)button;
				_game.Post(new MouseDownInputMessage
					{Position = where, Code = translate});
			}
		}

		/// <summary>
		/// Called when the mouse moves.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnMouseMove(MouseDevice sender)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
			{
				var where = new Vector2(sender.X, sender.Y);
				var delta = new Vector2(sender.XDelta, sender.YDelta);
				_game.Post(new MouseMoveInputMessage
					{Position = where, Delta = delta});
			}
		}

		/// <summary>
		/// Called when the mouse button is released.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <param name="button">The button released.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnMouseUp(MouseDevice sender,
		                      MouseButton button)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
			{
				var where = new Vector2(sender.X, sender.Y);
				var translate = (int)button;
				_game.Post(new MouseUpInputMessage
					{Position = where, Code = translate});
			}
		}

		/// <summary>
		/// Called when the mouse wheel moves.
		/// </summary>
		/// <param name="sender">The sending device.</param>
		/// <param name="delta">The amount the wheel moved.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void OnMouseWheel(MouseDevice sender, int delta)
		{
			if (_disposed)
				throw new ObjectDisposedException("InputTranslator");
			if (_game != null)
			{
				var where = new Vector2(sender.X, sender.Y);
				_game.Post(new MouseWheelInputMessage
					{Position = where, Ticks = delta});
			}
		}

		/// <summary>
		/// Unlists this instance from the <see cref="GameWindow"/>
		/// events.
		/// </summary>
		private void Unlist()
		{
			_gameWindow.Keyboard.KeyDown -= OnKeyDown;
			_gameWindow.Keyboard.KeyUp -= OnKeyUp;
			//_gameWindow.Keyboard.Character -= OnCharacter;
			//_gameWindow.Mouse.Move -= OnMouseMove;
			_gameWindow.Mouse.ButtonDown -= OnMouseDown;
			_gameWindow.Mouse.ButtonUp -= OnMouseUp;
		}
	}
}